package org.osomit.sacct.remoting.socket;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osomit.sacct.io.iface.ServerReaderWriter;
import org.osomit.sacct.entity.*;
import org.osomit.sacct.service.handler.iface.ServiceHandler;
import org.osomit.sacct.security.utils.SecurityUtil;
import org.osomit.sacct.security.utils.SharedSecurityGenerator;
import org.osomit.sacct.session.manager.iface.SessionManager;
import org.osomit.sacct.session.token.impl.SessionToken;
import org.osomit.sacct.builder.RequestBuilder;
import org.stringtree.json.JSONReader;

import javax.crypto.SecretKey;
import java.net.Socket;
import java.io.*;

import com.google.inject.Inject;

/**
 *
 * @author Jian Fang (John.Jian.Fang@gmail.com)
 *
 * Date: Jun 26, 2009
 *
 */
public class SocketWorker implements Runnable{
    private Socket socket;
    private String sockId;
    private String clientId;
    private ServerReaderWriter serverReaderWriter;
    private ServiceHandler handler;
    private SessionManager sessionManager;
    private RequestBuilder requestBuilder = new RequestBuilder();
    private JSONReader jsreader = new JSONReader();
    
    private Log log = LogFactory.getLog(SocketWorker.class);

    public ServiceHandler getHandler() {
        return handler;
    }
    
    @Inject
    public void setHandler(ServiceHandler handler) {
        this.handler = handler;
    }

    public ServerReaderWriter getServerReaderWriter() {
        return serverReaderWriter;
    }

    @Inject
    public void setServerReaderWriter(ServerReaderWriter serverReaderWriter) {
        this.serverReaderWriter = serverReaderWriter;
    }

    public SessionManager getSessionManager() {
        return sessionManager;
    }

    @Inject
    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    public SocketWorker(){

    }

    public void initalize(Socket socket){
        this.socket = socket;
        this.clientId = socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
        this.sockId = "service socket @ :" + socket.getLocalPort();
    }

    public SocketWorker(Socket socket) {
        this.socket = socket;
        this.clientId = socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
        this.sockId = "service socket @ :" + socket.getLocalPort();
    }

    protected WorkflowContext convert(RequestContext context){
        WorkflowContext wfc = new WorkflowContext();
        wfc.setClientId(context.getClientId());
        wfc.setLocale(context.getLocale());
        wfc.setRequestId(context.getRequestId());
        
        return wfc;
    }

    protected Request processRequest(SecretKey sessionKey, SecureRequest request) {
        String requestInString;
        if(sessionKey != null){
            requestInString = SecurityUtil.decrypt(sessionKey, request.getRequest());
        }else{
            requestInString = SecurityUtil.base64Decode(request.getRequest());
        }

        if(requestInString != null){
            Object obj = jsreader.read(requestInString);
            return  requestBuilder.build(obj);
        }

        return null;
    }

    protected SecureResponse processResponse(SecretKey sessionKey, Response response){
        SecureResponse sresp = new SecureResponse();
        response.setRnd(SecurityUtil.base64Encode(SharedSecurityGenerator.generateSharedSecret(64)));
        String respInString = response.toJSON().toString();
        if(sessionKey != null){
            String encrypted = SecurityUtil.encrypt(sessionKey, respInString);
            sresp.setResponse(encrypted);
        }else{
            sresp.setResponse(SecurityUtil.base64Encode(respInString));
        }

        return sresp;
    }

    public boolean process(OutputStream out, BufferedReader in){
        boolean running = true;

        log.debug(sockId + " reading from socket for " + clientId);

        SecureRequest secureRequest = this.serverReaderWriter.read(in);

        if(secureRequest == null)
            return false;

        log.debug(sockId + " read data from " + clientId + ": " + secureRequest.toJSON().toString() + "\n");
        String sid = secureRequest.getSessionId();
        SecretKey sessionKey = null;

        if (sid != null) {
            SessionToken token = this.sessionManager.getToken(sid);
            sessionKey = token.getSessionKey();
        }

        Request request = this.processRequest(sessionKey, secureRequest);
        if (request == null) {
            log.warn("Request from " + clientId + " is empty!");
        }else{
            if("closeSession".equals(request.getCommand())){
                log.debug("Received close session request from " + clientId);
                running = false;
            }
            WorkflowContext wfc = this.convert(request.getRequestContext());
            wfc.setSessionId(sid);
            request.setRequestContext(wfc);
        }

        Response resp = handler.process(request);
        SecureResponse secureResponse = this.processResponse(sessionKey, resp);

        this.serverReaderWriter.write(out, secureResponse);

        String strResp = secureResponse.toJSON().toString();
        log.debug("RESPONSE: " + strResp + "\n");

        return running;
    }

    public void run() {
        // read and service request
        log.info(sockId + " socket handler started for " + clientId);

        try {

            log.debug(sockId + " creating streams for " + clientId + "\n");
            
            OutputStream out = this.socket.getOutputStream();

            BufferedReader in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            boolean running = true;

            while(running){
                running = process(out, in);
            }

//            in.close();
//            out.close();
        } catch (IOException e) {
            log.error("Exception while handling request from " + clientId + " " + e.getMessage());
        }

        log.info(sockId + " socket handler finished for " + clientId);
    }
}
